﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Chat.GUI;

namespace Chat.Server.GUI
{
    /// <summary>
    /// The Chat server GUI.
    /// </summary>
    public partial class ServerMainForm : Form
    {
        /// <summary>
        /// The server core engine.
        /// </summary>
        private Server server;
        /// <summary>
        /// Dialog to set the IP address and the port number the server will listen on,
        /// for new incomming connections from clients.
        /// </summary>
        private IPEndPointDialog listenOnDialog;

        public ServerMainForm()
        {
            InitializeComponent();

            listenOnDialog = new IPEndPointDialog();
            listenOnDialog.Text = "Listen On";
        }

        /// <summary>
        /// Delegate to invoke the UpdateUsers method.
        /// </summary>
        protected delegate void UpdateUsersDelegate(
            ICollection<NickName> nn, UserConnectState cs);
        /// <summary>
        /// Updates the tab pages of the tabControlUsers tab control.
        /// This tab control contains one tab of currently connected users
        /// and one tab for connect/disconnect history. All these tabs get
        /// updated upon user connection/disconnection.
        /// </summary>
        /// <param name="users">The collection of users that have connected/disconnected</param>
        /// <param name="connectState">Whether it have been connect or disconnect</param>
        protected virtual void UpdateUsers(
            ICollection<NickName> users, UserConnectState connectState)
        {
            if (tabControlUsers.InvokeRequired)
            {
                tabControlUsers.Invoke(
                    new UpdateUsersDelegate(UpdateUsers),
                    new object[] { users, connectState });

                return;
            }

            switch (connectState)
            {
                case UserConnectState.Connected:
                    UsersConnected(users);
                    break;
                case UserConnectState.Disconnected:
                    UsersDisconnected(users);
                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// Delegate to invoke ClearCurrentUsers() method
        /// </summary>
        protected delegate void ClearCurrentUsersDelegate();
        /// <summary>
        /// Clears the lstvCurrentUsers listview control which displays the list
        /// of currently connected users.
        /// </summary>
        protected virtual void ClearCurrentUsers()
        {
            if (lstvCurrentUsers.InvokeRequired)
            {
                lstvCurrentUsers.Invoke(new ClearCurrentUsersDelegate(ClearCurrentUsers));
                return;
            }

            lstvCurrentUsers.Items.Clear();
        }

        /// <summary>
        /// Updates the tab pages of the tabControlUsers tab control against
        /// the specified collection of new connected users.
        /// </summary>
        /// <param name="users">The collection of nicknames of connected users.</param>
        protected virtual void UsersConnected(ICollection<NickName> users)
        {
            foreach (NickName user in users)
            {
                ListViewItem item = new ListViewItem();

                item.Name = user.GetHashCode().ToString();

                item.SubItems[0].Text = user.Value;
                item.SubItems.Add("Connected");
                item.SubItems.Add(DateTime.Now.ToString());

                lstvCurrentUsers.Items.Insert(0, item);

                ListViewItem histItem = (ListViewItem)item.Clone();

                histItem.Name = string.Empty;
                lstvUserHistory.Items.Insert(0, histItem);
            }
        }
        /// <summary>
        /// Updates the tab pages of the tabControlUsers tab control against
        /// the specified collection of disconnected users.
        /// </summary>
        /// <param name="users">The collection of nicknames of disconnected users.</param>
        protected virtual void UsersDisconnected(ICollection<NickName> users)
        {
            foreach (NickName user in users)
            {
                lstvCurrentUsers.Items.RemoveByKey(user.GetHashCode().ToString());

                ListViewItem histItem = new ListViewItem();

                histItem.SubItems[0].Text = user.Value;
                histItem.SubItems.Add("Disconnected");
                histItem.SubItems.Add(DateTime.Now.ToString());

                lstvUserHistory.Items.Insert(0, histItem);
            }
        }

        /// <summary>
        /// Delegate for invocation the methods, that perform control updates upon
        /// the server state change from stopped to started or vice versa.
        /// </summary>
        /// <param name="started">Whether the server started or stopped.</param>
        protected delegate void UpdateServerStateLayoutDelegate(bool started);
        /// <summary>
        /// Updates ToollStripStatusLabel of the form, which idicates the server
        /// state (stopped or started).
        /// </summary>
        /// <param name="started">Whether the server started or stopped.</param>
        protected virtual void UpdateServerStateLabel(bool started)
        {
            if (statusStripServer.InvokeRequired)
            {
                statusStripServer.Invoke(
                    new UpdateServerStateLayoutDelegate(UpdateServerStateLabel),
                    new object[] { started });
                return;
            }

            toolStripStatusLblServerState.Text = started ? "Started" : "Stopped";
            toolStripStatusLblServerState.ForeColor = started ? Color.Blue : Color.Red;
        }
        /// <summary>
        /// Updates the main menu layout which is different for stopped and started
        /// server's states.
        /// </summary>
        /// <param name="started">Whether the server started or stopped.</param>
        protected virtual void UpdateMainMenuLayout(bool started)
        {
            if (menuStripMain.InvokeRequired)
            {
                menuStripMain.Invoke(
                    new UpdateServerStateLayoutDelegate(UpdateMainMenuLayout),
                    new object[] { started });
                return;
            }

            startToolStripMenuItem.Enabled = !started;
            stopToolStripMenuItem.Enabled = started;
        }
        /// <summary>
        /// Starts the server.
        /// Will start the listener of incoming client connections.
        /// </summary>
        private void StartServer()
        {
            try
            {
                //
                // start the listener.
                server = new Server(listenOnDialog.IpEndPoint);

                //
                // Subscribe to the server events before we start in order not to
                // miss the events.
                server.UserConnection += new EventHandler<UserConnectEventArgs>(server_UserConnection);
                server.UsersConnection += new EventHandler<UsersConnectEventArgs>(server_UsersConnection);
                server.ServerFailed += new EventHandler(server_ServerFailed);

                server.Start();
            }
            catch (Exception ex)
            {
                server = null;
                MessageBox.Show("Failed to start server:\n" + ex.Message);
                return;
            }
            
            //
            // Update the main form layout due to the server status change.
            UpdateServerStateLayout(true);
        }

        /// <summary>
        /// Stops the server.
        /// </summary>
        private void StopServer()
        {
            try
            {
                server.Stop();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Failed to stop server:\n" + ex.Message);
                return;
            }

            server = null;

            UpdateServerStateLayout(false);
        }

        private void ServerMainForm_Load(object sender, EventArgs e)
        {
            UpdateServerStateLayout(false);
        }

        /// <summary>
        /// Event handler for UserConnection event of the Server class.
        /// </summary>
        /// <param name="sender">The instanse of the Server class rose the event.</param>
        /// <param name="e">Arguments for the UserConnection event.</param>
        void server_UserConnection(object sender, UserConnectEventArgs e)
        {
            LinkedList<NickName> users = new LinkedList<NickName>();

            users.AddLast(e.User);

            //
            // Update controls displaying chat users.
            UpdateUsers(users, e.ConnectState);

            users.Clear();
        }

        /// <summary>
        /// Event handler for ServerFailed event of the Server class.
        /// Will close the main form.
        /// </summary>
        /// <param name="sender">The instanse of the Server class rose the event.</param>
        /// <param name="e">Arguments for the ServerFailed.</param>
        void server_ServerFailed(object sender, EventArgs e)
        {
            MessageBox.Show("Oops, the server failed unexpectedly and will close");

            Close(); // close the form
        }

        /// <summary>
        /// Event handler for UsersConnection event of the Server class.
        /// </summary>
        /// <param name="sender">The instanse of the Server class rose the event.</param>
        /// <param name="e">Arguments for the UsersConnection event.</param>
        void server_UsersConnection(object sender, UsersConnectEventArgs e)
        {
            //
            // Update controls displaying chat users.
            UpdateUsers(e.Users, e.ConnectState);
        }

        private void startToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (server != null)
            {
                MessageBox.Show("Server already running");
                return;
            }

            DialogResult dr = listenOnDialog.ShowDialog();

            if (dr == DialogResult.OK)
                StartServer();
        }

        private void stopToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (server == null)
            {
                MessageBox.Show("Server is not running");
                return;
            }

            StopServer();
        }

        /// <summary>
        /// Updtates layout of all controls of the main form which are subject to change
        /// upon the server state change (started/stopped)
        /// </summary>
        /// <param name="started">Whether the server started or stopped.</param>
        private void UpdateServerStateLayout(bool started)
        {
            // update the toolstrip status label.
            UpdateServerStateLabel(started);
            // update the main menu.
            UpdateMainMenuLayout(started);
            // clear the list of current users if stopped.
            if (!started)
                ClearCurrentUsers();
        }

        /// <summary>
        /// When the form closed.
        /// Will stop the server if not stopped yet.
        /// Will save the application settings of the server's IP and port number it listens on for incomming connections.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ServerMainForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (server != null)
                server.Stop();

            if (listenOnDialog == null || listenOnDialog.IpEndPoint == null)
                return;

            Chat.GUI.Properties.Settings.Default.DefaultServerIp =
                listenOnDialog.IpEndPoint.Address.ToString();
            Chat.GUI.Properties.Settings.Default.DefaultServerPort =
                listenOnDialog.IpEndPoint.Port;
            Chat.GUI.Properties.Settings.Default.Save();
        }
    }
}
